/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package ua.yandex.collections;

import java.util.Arrays;

/**
 *
 * @author vlad
 */
public class MyLinkedList implements MyList {

    private static class Node {

        private Object data;
        private Node next;
        private Node prev;

        public Node(Object data, Node prev, Node next) {
            this.data = data;
            this.prev = prev;
            this.next = next;
        }

    }

    private Node first = null;
    private Node last = null;
    private int size = 0;

    public MyLinkedList() {
        first = null;
        last = null;
        size = 0;
    }

    public MyLinkedList(Object[] c) {
        this.addAll(c);
    }

    @Override
    public String toString() {
        return Arrays.toString(this.toArray());
    }

    private Node getNode(final int index) {
        Node iter = first;
        int loop = index;
        while (loop-- != 0 && iter != null) {
            iter = iter.next;
        }
        return iter;
    }

    private boolean inBounds(int index) {
        return index < size && index >= 0;
    }

    public void addFirst(Object e) {
        add(0, e);
    }

    public void addLast(Object e) {
        add(e);
    }

    public Object getFirst() {
        if (size == 0) {
            throw new NullPointerException("No elements in list");
        }
        return get(0);
    }

    public Object getLast() {
        if (size == 0) {
            throw new NullPointerException("No elements in list");
        }
        return last.data;
    }

    public Object removeFirst() {
        if (size == 0) {
            throw new NullPointerException("No elements in list");
        }
        return remove(0);
    }

    public Object removeLast() {
        if (size == 0) {
            throw new NullPointerException("No elements in list");
        }
        Node popped = last;
        if (popped != first) {
            popped.prev.next = popped.next;
        } else {
            first = first.next;
        }
        last = last.prev;
        size--;
        return popped.data;
    }

    @Override
    public void add(Object e) {
        Node node = new Node(e, last, null);
        if (last == null) {
            first = node;
            last = node;
            size = 1;
        } else {
            last.next = node;
            last = node;
            size++;
        }
    }

    @Override
    public void add(int index, Object e) {
        if (size == index) {
            this.add(e);
        } else {
            if (!inBounds(index)) {
                throw new IndexOutOfBoundsException("MyLinkedList boundaries"
                        + " have been violated");
            }
            Node nextN = this.getNode(index);
            Node prevN = this.getNode(index - 1);
            Node node = new Node(e, prevN, nextN);
            nextN.prev = node;
            if (prevN != null) {
                prevN.next = node;
            } else {
                first = node;
            }
            size++;
        }
    }

    @Override
    public void addAll(Object[] c) {
        for (Object e : c) {
            this.add(e);
        }
    }

    @Override
    public void addAll(int index, Object[] c) {
        if (size == index) {
            this.addAll(c);
        } else {
            if (!inBounds(index)) {
                throw new IndexOutOfBoundsException("MyLinkedList boundaries"
                        + " have been violated");
            }
            Node nextN = this.getNode(index);
            Node prevN = this.getNode(index - 1);
            MyLinkedList patch = new MyLinkedList();
            patch.addAll(c);
            nextN.prev = patch.last;
            patch.last.next = nextN;
            if (prevN != null) {
                prevN.next = patch.first;
                patch.first.prev = prevN;
            } else {
                first = patch.first;
            }
            size += c.length;
        }
    }

    @Override
    public Object get(int index) {
        if (!inBounds(index)) {
            throw new IndexOutOfBoundsException("MyLinkedList boundaries"
                    + " have been violated");
        }
        return getNode(index).data;
    }

    @Override
    public Object remove(int index) {
        if (!inBounds(index)) {
            throw new IndexOutOfBoundsException("MyLinkedList boundaries"
                    + " have been violated");
        }
        Node popped = getNode(index);
        if (popped != first) {
            popped.prev.next = popped.next;
        } else {
            first = first.next;
        }
        if (popped != last) {
            popped.next.prev = popped.prev;
        } else {
            last = last.prev;
        }
        size--;
        return popped.data;
    }

    @Override
    public void set(int index, Object e) {
        if (!inBounds(index)) {
            throw new IndexOutOfBoundsException("MyLinkedList boundaries"
                    + " have been violated");
        }
        Node changed = getNode(index);
        changed.data = e;
    }

    @Override
    public int indexOf(Object o) {
        Node iter = first;
        int pos = 0;
        while (iter != null && !(iter.data.equals(o))) {
            pos++;
            iter = iter.next;
        }
        if (iter == null) {
            return -1;
        }
        return pos;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void clear() {
        size = 0;
        first = null;
        last = null;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public Object[] toArray() {
        Object[] array = new Object[size];
        Node iter = first;
        int counter = 0;
        while (iter != null) {
            array[counter++] = iter.data;
            iter = iter.next;
        }
        return array;
    }

}
